﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Net;
using MovieBuddy.Helpers;
using MovieModel.Common;
using MovieModel.Json;
using MovieModel.Model;

namespace MovieModel.FileSystem
{
    public class FileSystemManager : Manager
    {

        private const string MoviesFile = "movies.json",
                             TagsFile = "tags.json",
                             PeopleFile = "people.json",
                             CompaniesFile = "companies.json";

        private const string ImageFolder = "images";

        public readonly DirectoryInfo Directory;
        public readonly DirectoryInfo ImageDirectory;

        public FileSystemManager(DirectoryInfo directory)
        {
            Directory = directory;
            ImageDirectory = new DirectoryInfo(directory.FullName + "/" + ImageFolder);
        }

        //TODO: this is kind of messy handling both files and web URIs.  Might want to break it into two functions...
        public override FileInfo ImportFile(Uri imageLocation, params ThumbnailRequest[] thumbnails)
        {
            RemoveDanglingFiles();

            FileInfo finalFile;
            FileInfo sourceFile = null; // if the source is a file, this won't be null
            if (imageLocation.IsFile) sourceFile = new FileInfo(imageLocation.PathAndQuery.Replace('/', Path.DirectorySeparatorChar));

            string name;
            if (sourceFile != null)
            {
                name = Path.GetFileNameWithoutExtension(sourceFile.Name);
            }
            else
            {
                name = Guid.NewGuid().ToString(); // give up on a name and just use a guid...
            }
            
            string baseName = ImageDirectory.FullName + "/" + name;
            if (sourceFile != null && sourceFile.Directory != null && sourceFile.Directory.Equals(ImageDirectory))
            {
                finalFile = sourceFile;
            }
            else
            {
                string extension;
                if (sourceFile != null)
                {
                    extension = sourceFile.Extension;
                }
                else
                {
                    string possibleExtension = imageLocation.PathAndQuery.Split('.').Last();
                    var extensions = MovieConstants.ImageExtensions;
                    if (extensions.Contains(possibleExtension.ToLower()))
                    {
                        extension = "." + possibleExtension;
                    }
                    else
                    {
                        extension = ".picture"; // whatever
                    }
                }
                finalFile = ChooseImageFilename(baseName, extension);
                if (sourceFile != null)
                {
                    sourceFile.CopyTo(finalFile.FullName);
                }
                else
                {
                    new WebClient().DownloadFile(imageLocation, finalFile.FullName);
                }
                
            }

            var primary = Image.FromFile(finalFile.FullName);

            double aspectRatio = primary.Width/((double) primary.Height);

            foreach (var thumb in thumbnails)
            {
                // ReSharper disable PossibleInvalidOperationException
                int width = thumb.Width ?? (int) (thumb.Height.Value*aspectRatio);
                int height = thumb.Height ?? (int) (thumb.Width/aspectRatio);
                // ReSharper restore PossibleInvalidOperationException

                string desiredName = baseName + "_thumb_" + width + "x" + height;
                var thumbFile = ChooseImageFilename(desiredName, ".jpg");
                var thumbImage = primary.GetThumbnailImage(width, height, () => false, IntPtr.Zero);
                thumbImage.Save(thumbFile.FullName, ImageFormat.Jpeg);
                thumb.File = thumbFile;
            }

            return finalFile;
        }

        private void RemoveDanglingFiles()
        {
            var allFiles = new HashSet<string>();
            foreach (var file in ImageDirectory.GetFiles()) allFiles.Add(file.FullName);

            foreach (var movie in Movies.Values)
            {
                if (movie.ImageLocation != null) allFiles.Remove(movie.ImageLocation);
                if (movie.ThumbnailLocation != null) allFiles.Remove(movie.ThumbnailLocation);
            }

            foreach (var file in allFiles)
            {
                try
                {
                    new FileInfo(file).Delete();
                }
                catch (Exception)
                {
                    
                }
            }
        }

        private static FileInfo ChooseImageFilename(string baseName, string extension)
        {
            int num = 0;
            while(true)
            {
                var file = new FileInfo(baseName + (num == 0 ? "" : ("(" + num + ")")) + extension);
                if (!file.Exists) return file;
                num++;
            }
        }

        public override bool IsStorageReadyForInitialization()
        {
            return !Directory.Exists || !Directory.GetFileSystemInfos().Any();
        }

        public override void InitializeStorage()
        {
            if (!IsStorageReadyForInitialization()) 
                throw new Exception("Cannot initialize storage in a directory which exists and has files in it: " + Directory.FullName);
            if (!Directory.Exists) Directory.Create();
            ImageDirectory.Create();

            var ms = new MovieSerializer();
            using (var sw = new StreamWriter(new FileInfo(Directory.FullName + "/" + MoviesFile).OpenWrite()))
            {
                ms.SaveMovies(sw, new Movie[]{});
            }
            using (var sw = new StreamWriter(new FileInfo(Directory.FullName + "/" + TagsFile).OpenWrite()))
            {
                ms.SaveTags(sw, new Tag[] { });
            }
            using (var sw = new StreamWriter(new FileInfo(Directory.FullName + "/" + PeopleFile).OpenWrite()))
            {
                ms.SavePeople(sw, new Person[] { });
            }
            using (var sw = new StreamWriter(new FileInfo(Directory.FullName + "/" + CompaniesFile).OpenWrite()))
            {
                ms.SaveCompanies(sw, new Company[] { });
            }
        }

        public override void Load(ProgressWatcher progress)
        {
            var ms = new MovieSerializer();
            FileInfo[] files = Directory.GetFiles();
            FileInfo movies = GetFile(files, MoviesFile);
            FileInfo tags = GetFile(files, TagsFile);
            FileInfo people = GetFile(files, PeopleFile);
            FileInfo companies = GetFile(files, CompaniesFile);
            progress.Total = 6;  // movies counts as 3

            Tags = ms.ParseTags(tags.OpenText());
            People = ms.ParsePeople(people.OpenText());
            Companies = ms.ParseCompanies(companies.OpenText());
            Movies = ms.ParseMovies(movies.OpenText(), this);

            foreach (var t in Tags.Values) t.ItemUpdated += TagUpdated;
            foreach (var m in Movies.Values) m.ItemUpdated += MovieUpdated;
            foreach (var c in Companies.Values) c.ItemUpdated += CompanyUpdated;
            foreach (var p in People.Values) p.ItemUpdated += PersonUpdated;
        }

        public override void SaveMovies()
        {
            var ms = new MovieSerializer();
            FileInfo[] files = Directory.GetFiles();
            FileInfo movies = GetFile(files, MoviesFile);
            
            using (var sw = new StreamWriter(movies.OpenWrite()))
            {
                ms.SaveMovies(sw, Movies.Values);
            }
        }

        public override void SaveTags()
        {
            var ms = new MovieSerializer();
            FileInfo[] files = Directory.GetFiles();
            FileInfo tags = GetFile(files, TagsFile);

            using (var sw = new StreamWriter(tags.OpenWrite()))
            {
                ms.SaveTags(sw, Tags.Values);
            }
        }

        public override void SavePeople()
        {
            var ms = new MovieSerializer();
            FileInfo[] files = Directory.GetFiles();
            FileInfo people = GetFile(files, PeopleFile);

            using (var sw = new StreamWriter(people.OpenWrite()))
            {
                ms.SavePeople(sw, People.Values);
            }
        }

        public override void SaveCompanies()
        {
            var ms = new MovieSerializer();
            FileInfo[] files = Directory.GetFiles();
            FileInfo companies = GetFile(files, CompaniesFile);

            using (var sw = new StreamWriter(companies.OpenWrite()))
            {
                ms.SaveCompanies(sw, Companies.Values);
            }
        }

        private FileInfo GetFile(FileInfo[] files, string name)
        {
            Contract.Requires(files != null);
            var file = files.FirstOrDefault(f => f.Name == name);
            if (file == null)
                throw new UserFriendlyException("Missing file: " + Directory.FullName + Path.DirectorySeparatorChar + name);
            return file;
        }
    }
}
